/*
 * Copyright 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package be.free.ts.support.web;

import com.google.common.collect.Lists;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.inject.client.AsyncProvider;
import com.google.gwt.user.client.rpc.AsyncCallback;
import net.customware.gwt.presenter.client.*;

import java.util.List;

/**
 * @author Christophe De Blende
 * @since 07/01/13
 */
public abstract class AsyncBasicPresenter<D extends Display> implements Presenter, AsyncCallback<D> {

	/**
	 * The display for the presenter.
	 */
	private final AsyncProvider<D> displayAsync;

	protected D display;

	/**
	 * The {@link net.customware.gwt.presenter.client.EventBus} for the application.
	 */
	protected final EventBus eventBus;

	private final List<HandlerRegistration> handlerRegistrations = Lists.newArrayList();

	private boolean bound = false;

	protected AsyncBasicPresenter(AsyncProvider<D> display, EventBus eventBus) {
		this.displayAsync = display;
		this.eventBus = eventBus;
	}

	public void bind() {
		if (!bound) {
			displayAsync.get(this);
			onBind();
			bound = true;
		}
	}

	@Override
	public void onFailure(Throwable caught) {
		GWT.log(caught.getMessage(), caught);
	}

	@Override
	public void onSuccess(D result) {
		display = result;
	}

	/**
	 * Any {@link HandlerRegistration}s added will be removed when
	 * {@link #unbind()} is called. This provides a handy way to track event
	 * handler registrations when binding and unbinding.
	 *
	 * @param handlerRegistration The registration.
	 */
	protected void registerHandler(HandlerRegistration handlerRegistration) {
		handlerRegistrations.add(handlerRegistration);
	}

	public void unbind() {
		if (bound) {
			bound = false;

			for (HandlerRegistration reg : handlerRegistrations) {
				reg.removeHandler();
			}
			handlerRegistrations.clear();

			onUnbind();
		}

	}

	/**
	 * This method is called when binding the presenter. Any additional bindings
	 * should be done here.
	 */
	protected abstract void onBind();

	/**
	 * This method is called when unbinding the presenter. Any handler
	 * registrations recorded with {@link #registerHandler(HandlerRegistration)}
	 * will have already been removed at this point.
	 */
	protected abstract void onUnbind();

	/**
	 * Checks if the presenter has been bound. Will be set to false after a call
	 * to {@link #unbind()}.
	 *
	 * @return The current bound status.
	 */
	public boolean isBound() {
		return bound;
	}

	/**
	 * Returns the display for the presenter.
	 *
	 * @return The display.
	 */
	public D getDisplay() {
		return display;
	}

	/**
	 * Fires a {@link net.customware.gwt.presenter.client.PresenterChangedEvent} to the {@link EventBus}.
	 * Call this method any time the presenter's state has been modified.
	 */
	protected void firePresenterChangedEvent() {
		PresenterChangedEvent.fire(eventBus, this);
	}

	/**
	 * Fires a {@link net.customware.gwt.presenter.client.PresenterRevealedEvent} to the {@link EventBus}.
	 * Implementations should call this when the presenter has been
	 * revealed onscreen.
	 *
	 * @param originator If set to true, this specifies that this presenter
	 *                   was the originator of the 'revelation' request.
	 */
	void firePresenterRevealedEvent(boolean originator) {
		PresenterRevealedEvent.fire(eventBus, this, originator);
	}

	/**
	 * Triggers a {@link PresenterRevealedEvent}. Subclasses should override
	 * this method and call <code>super.revealDisplay()</code> if they need to
	 * perform extra operations when being revealed.
	 */
	public void revealDisplay() {
		onRevealDisplay();
		firePresenterRevealedEvent(true);
	}

	/**
	 * Called before firing a {@link net.customware.gwt.presenter.client.PresenterRevealedEvent}.
	 */
	protected abstract void onRevealDisplay();
}
